using System;
using System.Threading;

namespace EOSDigital.API
{
	public class STAThread
	{
		public static readonly object ExecLock = new object();

		private Thread mainThread;

		private bool isRunning;

		private object runLock = new object();

		private object cmdLock = new object();

		protected object threadLock1 = new object();

		private object threadLock2 = new object();

		protected bool block1 = true;

		private bool block2 = true;

		private Action runAction;

		private Exception runException;

		public static bool IsSTAThread
		{
			get
			{
				return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA;
			}
		}

		public bool IsRunning
		{
			get
			{
				return this.isRunning;
			}
		}

		public int ThreadID
		{
			get
			{
				return this.mainThread.ManagedThreadId;
			}
		}

		internal STAThread()
		{
		}

		public void Start()
		{
			lock (this.runLock)
			{
				if (!this.isRunning)
				{
					this.mainThread = STAThread.CreateThread(this.ExecutionLoop);
					this.isRunning = true;
					this.mainThread.Start();
					this.WaitForThread();
				}
			}
		}

		public void Shutdown()
		{
			lock (this.runLock)
			{
				if (this.isRunning)
				{
					this.isRunning = false;
					this.NotifyThread();
					this.mainThread.Join();
				}
			}
		}

		public void Invoke(Action action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			if (Monitor.TryEnter(this.cmdLock))
			{
				try
				{
					action();
				}
				finally
				{
					Monitor.Exit(this.cmdLock);
				}
			}
			else
			{
				lock (this.runLock)
				{
					if (!this.isRunning)
					{
						throw new InvalidOperationException("Thread is not running");
					}
					this.runAction = action;
					this.NotifyThread();
					this.WaitForThread();
					if (this.runException != null)
					{
						throw new ExecutionException(this.runException.Message, this.runException);
					}
				}
			}
		}

		public T Invoke<T>(Func<T> func)
		{
			if (func == null)
			{
				throw new ArgumentNullException("func");
			}
			T result = (T)default(T);
			this.Invoke(delegate
			{
				result = func();
			});
			return (T)result;
		}

		public static Thread CreateThread(Action action)
		{
			Thread thread = new Thread(action.Invoke);
			thread.SetApartmentState(ApartmentState.STA);
			return thread;
		}

		public static void ExecuteThread(Action action, bool wait)
		{
			Exception runException = null;
			Thread thread = STAThread.CreateThread(delegate
			{
				try
				{
					action();
				}
				catch (Exception ex)
				{
					if (wait)
					{
						runException = ex;
						goto end_IL_000d;
					}
					throw;
					end_IL_000d:;
				}
			});
			thread.Start();
			if (!wait)
			{
				return;
			}
			thread.Join();
			if (runException == null)
			{
				return;
			}
			throw new ExecutionException(runException.Message, runException);
		}

		public static T ExecuteThread<T>(Func<T> func)
		{
			Exception runException = null;
			T result = (T)default(T);
			Thread thread = STAThread.CreateThread(delegate
			{
				try
				{
					result = func();
				}
				catch (Exception ex)
				{
					Exception ex2 = runException = ex;
				}
			});
			thread.Start();
			thread.Join();
			if (runException != null)
			{
				throw new ExecutionException(runException.Message, runException);
			}
			return (T)result;
		}

		private void NotifyThread()
		{
			lock (this.threadLock1)
			{
				this.block1 = false;
				Monitor.Pulse(this.threadLock1);
			}
		}

		private void WaitForThread()
		{
			lock (this.threadLock2)
			{
				while (this.block2)
				{
					Monitor.Wait(this.threadLock2);
				}
				this.block2 = true;
			}
		}

		private void ReleaseWait()
		{
			lock (this.threadLock2)
			{
				this.block2 = false;
				Monitor.Pulse(this.threadLock2);
			}
		}

		protected virtual void WaitForNotification()
		{
			lock (this.threadLock1)
			{
				while (this.block1 && this.isRunning)
				{
					Monitor.Wait(this.threadLock1);
				}
				this.block1 = true;
			}
		}

		private void ExecutionLoop()
		{
			this.ReleaseWait();
			try
			{
				lock (this.cmdLock)
				{
					while (this.isRunning)
					{
						this.WaitForNotification();
						if (!this.isRunning)
						{
							break;
						}
						this.runException = null;
						try
						{
							lock (STAThread.ExecLock)
							{
								this.runAction();
							}
						}
						catch (Exception ex)
						{
							Exception ex2 = this.runException = ex;
						}
						this.ReleaseWait();
					}
				}
			}
			finally
			{
				this.isRunning = false;
				this.ReleaseWait();
			}
		}
	}
}
